1장. 랭체인 살펴보기
출처 — 브라이스 유·조경아·박수진·김재웅, 『RAG 마스터: 랭체인으로 완성하는 LLM 서비스』(프리렉, 2025), 1장 (pp. 27~102). 원문 PDF
rag_master_final_v11_260910.pdf(2026-09-10 판)랭체인이 왜 필요한지부터 시작해, 패키지 구성과 버전 변화, 대규모 언어 모델을 다루는 두 가지 방식(오픈AI API 직접 호출 vs 랭체인), 러너블을 체인으로 엮는 랭체인 표현 언어(LCEL), 프롬프트와 출력 파서, 그리고 챗봇이 대화를 기억하게 만드는 메모리 관리까지 — 이 책 전체가 딛고 설 기초를 다진다.
학습 목표
이 장을 끝내면 다음을 할 수 있다.
- 랭체인 생태계의 핵심 패키지(langchain-core·langchain·langchain-community·파트너 패키지·랭그래프·랭서브·랭스미스)의 역할을 구분한다.
- 오픈AI API를 직접 호출하는 코드와 랭체인(LCEL)으로 같은 작업을 구현하는 코드를 작성하고 장단점을 비교한다.
- 러너블 표준 인터페이스(invoke·batch·stream 등)를 사용해 체인을 구성하고, 파이프 연산자(
|)와.pipe()메서드로 여러 러너블을 연결한다. - 문자열·챗·퓨샷 프롬프트 템플릿을 상황에 맞게 선택해 작성하고, 출력 파서로 모델 응답을 구조화된 데이터로 변환한다.
- 대화 이력을 관리하는 네 가지 방법(수동 전달·
ChatMessageHistory·RunnableWithMessageHistory·트리밍/요약)의 차이를 설명하고 상황에 맞게 구현한다.
전체 흐름도
[ LLM의 한계 ] ── 최신 정보·도메인 지식 반영이 어려움
│ 검색 증강 생성(RAG)으로 보완
▼
[ 랭체인(LangChain) ] ── LLM 애플리케이션 개발을 위한 오픈소스 프레임워크
│
├─ 패키지: langchain-core(기반) · langchain(체인·에이전트·검색기)
│ · langchain-community(타사 통합) · 파트너 패키지(langchain-openai 등)
│ · 랭그래프(그래프 워크플로) · 랭서브(REST 배포) · 랭스미스(모니터링)
│
├─ 버전: 0.1(패키지 분리) → 0.2(커뮤니티 의존성 제거) → 0.3(Pydantic 2·Python 3.8 종료)
▼
[ 대규모 언어 모델 ] ── 오픈AI API 직접 호출 vs 랭체인 경유
│ 하이퍼파라미터: temperature · max_tokens · top_p · frequency/presence penalty · stop
▼
[ 랭체인 표현 언어(LCEL) ] ── 러너블(Runnable)을 파이프(|)·.pipe()로 연결
│ invoke · batch · stream (+ 비동기 버전) · RunnableParallel · RunnablePassthrough
▼
[ 프롬프트 ] ── 문자열 / 챗 / 메시지 자리 표시자 / 퓨샷(예제 선택기) / 프롬프트 허브
▼
[ 출력 파서 ] ── JSON · Pydantic · CSV 등으로 자유 형식 텍스트를 구조화된 데이터로 변환
│ get_format_instructions() · parse() · parse_with_prompt()
▼
[ 메모리 관리 ] ── 대화 이력 유지
│ 수동 전달 → ChatMessageHistory → RunnableWithMessageHistory(자동)
│ → 메시지 트리밍 / 대화 요약(길어진 대화의 효율화)
▼
[ 2장 이후 ] RAG 기초·멀티모달·고도화 전략·그래프 RAG·랭그래프·에이전트·파인튜닝으로 확장
0. 용어 사전
참고 — 위쪽 4개는 이 장을 읽기 전에 알아야 하는 선행 용어다. 이 책의 0장(실습 환경 설정)에서 다루는 개념이며, 이 장은 이미 알고 있다는 전제로 코드를 시작한다.
| 한글 용어 | 원문 영문명 | 의미 |
|---|---|---|
| API 키 | API Key | (선행) 오픈AI 같은 모델 제공자의 서비스에 인증하고 요청을 보낼 때 쓰는 식별 문자열. 코드에 직접 적지 않고 .env 파일에 보관한다. 0장 §4·§5, 본문 §2.1 |
| .env 파일 | .env File | (선행) 키=값 형식으로 환경 변수를 적어 두는 텍스트 파일. load_dotenv()로 읽어 os.getenv()로 값을 꺼낸다. 0장 §5, 본문 §2.1 |
| 파이썬 패키지 관리자(pip) | pip | (선행) pip install <패키지명> 명령으로 파이썬 라이브러리를 설치하는 도구. 0장 §3, 본문 §1.1 |
| 구글 코랩 | Google Colab | (선행) 브라우저에서 바로 실행되는 무료 파이썬 노트북 환경. 이 책의 모든 실습 코드가 여기서 돌아간다. 0장 §2, 본문 §2.1 |
| 대규모 언어 모델(LLM) | Large Language Model | 방대한 텍스트 데이터를 학습해 자연어를 이해하고 생성하는 인공지능 모델. 서두 |
| 검색 증강 생성(RAG) | Retrieval-Augmented Generation | LLM의 생성 능력에 외부 검색 기능을 결합해 최신·도메인 정보를 반영하는 기법. 서두 |
| 랭체인 | LangChain | 대규모 언어 모델을 활용한 애플리케이션 개발을 위한 오픈소스 프레임워크. 서두 |
| 구성요소(컴포넌트) | Components | 랭체인이 제공하는, LLM 상호작용·메모리 관리·체인 실행 등을 담당하는 재사용 가능한 단위. §1 |
| langchain-core | langchain-core | 랭체인 생태계의 기반 패키지. 대규모 언어 모델·벡터 저장소·검색기의 기본 구조와 LCEL을 담는다. §1.1 |
| langchain | langchain | 체인·에이전트·검색기 전략을 포함하는 패키지. langchain-core에 의존한다. §1.1 |
| langchain-community | langchain-community | 커뮤니티가 유지 관리하는 다양한 타사 서비스 통합 모음 패키지. §1.1 |
| 파트너 패키지 | Partner Package | 오픈AI·앤트로픽 등 특정 서비스와의 통합을 전담하는 langchain-[partner] 형태의 패키지. §1.1 |
| 랭그래프 | LangGraph | 그래프 기반으로 복잡한 작업 흐름과 분기를 설계하는, 고수준 에이전트 생성 인터페이스를 제공하는 패키지. §1.1 |
| 랭서브 | LangServe | 랭체인의 체인을 REST API로 배포하는 패키지. §1.1 |
| 랭스미스 | LangSmith | 애플리케이션을 디버깅·테스트·평가·모니터링하는 개발자 플랫폼. §1.1 |
| 벡터 저장소 | Vector Store | 데이터를 숫자 배열(벡터) 형태로 저장하는 방식. §1.1 |
| 검색기 | Retriever | 필요한 정보를 찾아 주는 시스템. §1.1 |
| 하이퍼파라미터 | Hyperparameter | 모델이 생성하는 텍스트의 스타일·길이·정확도에 영향을 주는, 조정 가능한 속성값. §2.2 |
| 온도(temperature) | Temperature | 0~1 사이 값. 작을수록 예측 가능하고 일관된 출력을, 클수록 다양하고 창의적인 출력을 만든다. §2.2 |
| 최상위 P(top_p) | Top P | 특정 확률 분포 내 상위 P%의 토큰만 고려해 출력 다양성을 조정하는 파라미터. §2.2 |
| 러너블 | Runnable | 랭체인에서 작업을 실행하는 표준 단위. invoke·batch·stream 같은 공통 메서드를 갖는다. §3 |
| 랭체인 표현 언어(LCEL) | LangChain Expression Language | 러너블을 파이프(|)로 연결해 체인을 선언적으로("어떻게"가 아닌 "무엇을") 구성하는 방식. §3 |
| 체인 | Chain | 여러 러너블을 연결해 만든 하나의 작업 흐름. §1.1·§3 |
| RunnableParallel | RunnableParallel | 여러 체인을 동시에 병렬 실행하는 러너블. §3.2 |
| RunnablePassthrough | RunnablePassthrough | 입력 데이터를 가공 없이 다음 단계로 그대로 전달하는 러너블. §3.2·§6.4 |
| 프롬프트 템플릿 | Prompt Template | 사용자 입력과 매개변수를 모델에 대한 지침 문장으로 변환하는 틀. §4 |
| 챗 프롬프트 템플릿 | ChatPromptTemplate | 시스템·사용자·AI 역할이 구분된 메시지 시퀀스를 만드는 템플릿. §4.2 |
| 메시지 자리 표시자 | MessagesPlaceholder | 템플릿 안에서 동적으로 메시지 목록을 삽입할 자리를 예약하는 요소. §4.3 |
| 퓨샷 프롬프트 | Few-shot Prompt | 예제 입력·출력을 몇 개 제시해 모델이 더 정확하고 일관된 결과를 내도록 유도하는 기법. §4.4 |
| 예제 선택기 | Example Selector | 입력과 가장 유사한 예제만 골라 프롬프트에 포함시키는 도구. §4.4 |
| 프롬프트 허브 | Prompt Hub(LangChain Hub) | 프롬프트를 공유하고 버전을 관리하는 중앙 저장소. §4.5 |
| 출력 파서 | Output Parser | 모델이 생성한 자유 형식 텍스트를 JSON 등 구조화된 형식으로 변환하는 도구. §5 |
| PydanticOutputParser | PydanticOutputParser | 출력을 Pydantic 모델에 맞춰 구조화하고 데이터 검증까지 수행하는 파서. §5.2 |
| JsonOutputParser | JsonOutputParser | 출력을 JSON 형식으로 변환하며, 부분 생성된 JSON도 스트리밍으로 처리할 수 있는 파서. §5.4 |
| 대화 이력(메모리) | Chat History(Memory) | 챗봇이 이전 대화 내용을 기억해 답변에 반영하도록 관리하는 기능. §6 |
| ChatMessageHistory | ChatMessageHistory | 대화 메시지를 저장하고 재사용할 수 있게 관리하는 클래스. §6.2 |
| RunnableWithMessageHistory | RunnableWithMessageHistory | 세션별 대화 이력을 자동으로 불러오고 저장하는 러너블 래퍼. §6.3 |
| 메시지 트리밍 | Message Trimming | 오래된 메시지를 제거해 모델이 처리할 정보량을 줄이는 기법. §6.4 |
1. 랭체인 개요
챗GPT 등장 이후 대규모 언어 모델(LLM)이 질의응답·문서 요약·코드 생성 등에 널리 쓰이게 되었지만, LLM은 학습 시점의 데이터에 기반해 답하므로 최신 정보 반영이나 특정 도메인 지식 제공에 한계가 있다. 이를 보완하는 기술이 검색 증강 생성(RAG)이다. RAG는 LLM의 언어 생성 능력에 검색 기능을 결합해, 모델이 실시간으로 외부 정보를 검색하고 이를 반영해 더 정확하고 신뢰성 높은 답변을 만들도록 한다. 랭체인은 이런 LLM·RAG 기반 AI 애플리케이션을 더 쉽게 구축하도록 돕는 오픈소스 프레임워크다. 챗GPT나 Claude 같은 LLM을 쉽게 연결하고 활용하도록 다양한 구성요소와 타사 통합 기능을 제공하며, 이 구성요소들이 LLM과의 상호작용·메모리 관리·체인 실행·데이터 처리 같은 핵심 기능을 담당한다.
랭체인의 가장 큰 특징은 이름 그대로 "체인"처럼 각 기능을 유연하게 연결할 수 있다는 점이다. 문서 검색, 데이터 처리, 요약, 번역 같은 여러 작업을 레고 블록처럼 조립해 원하는 기능을 구현할 수 있고, 필요한 기능을 손쉽게 갈아 끼울 수 있어 프로젝트의 복잡성을 줄이고 개발 효율을 높인다. 이 모듈식 설계 덕분에 복잡한 LLM 애플리케이션을 단순하고 관리하기 쉽게 만들 수 있다.
1.1 랭체인 주요 패키지
랭체인 생태계는 여러 패키지로 구성되며, 각 패키지는 특정 역할을 수행한다. 개발자는 이 패키지들을 조합해 애플리케이션을 개발·배포한다. 각 패키지는 pip install <패키지명> 명령으로 설치한다(예: pip install langchain).
패키지 간 의존성은 방향 화살표로 표현된다 — 화살표는 소스 패키지가 대상 패키지에 종속됨을 뜻한다. langchain-core는 랭체인 생태계의 기반 패키지로 다른 많은 패키지가 이에 의존하며, langchain을 설치하면 langchain-core가 자동으로 함께 설치된다. langgraph는 langchain-core를 필수가 아닌 피어 의존성(peer dependency)으로 취급해 선택적으로만 사용한다. 패키지를 설치할 때 langchain-core 같은 필수 종속 패키지를 따로 설치할 필요는 없지만, 특정 버전에서만 지원되는 기능을 쓰려면 추가 패키지를 직접 설치하고 호환성을 확인해야 한다.
langchain-core. 랭체인의 중심 역할을 하는 패키지로, 대규모 언어 모델·벡터 저장소·검색기 같은 중요한 기능을 정의하는 기본 구조를 담는다. 여러 기능을 체인으로 연결할 수 있도록 랭체인 표현 언어(LCEL)를 여기서 제공한다. 핵심 기능만 담겨 있어 가볍고 효율적이다.
import langchain_core
langchain. 애플리케이션의 구조를 만드는 체인(chain), 대규모 언어 모델을 사용해 작업을 처리하는 지능형 시스템인 에이전트(agent), 정보를 검색하는 검색기(retriever) 전략 등을 포함한다. 특정 서비스에 국한되지 않고 다양한 환경에서 재사용할 수 있도록 설계되었다. langchain을 설치하면 langchain-core가 자동으로 설치된다.
import langchain
langchain-community. 랭체인 커뮤니티가 유지 관리하는 다양한 타사 서비스 통합(대규모 언어 모델·벡터 저장소·검색기 등)을 포함한다. 가능한 한 가볍게 설계되어 필요한 기능만 선택적으로 추가할 수 있다.
import langchain_community
파트너 패키지. 오픈AI·앤트로픽처럼 자주 쓰이는 타사 서비스 통합은 별도 패키지로 분리되어 langchain-[partner] 형태로 제공된다(예: langchain-openai, langchain-anthropic). 각 파트너 패키지가 특정 서비스만 전문적으로 다루므로 더 안정적이고 효율적으로 지원받을 수 있다.
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
랭그래프(LangGraph). 그래프 기반 모델링을 도와주는 패키지로, 여러 작업을 동시에 처리하거나 조건에 따라 분기하는 복잡한 애플리케이션을 설계할 수 있다. 지도를 그리듯 작업 흐름을 그래프로 표현하며, 고수준 인터페이스로 일반적인 에이전트를 쉽게 생성할 수 있다. 이 책 6·7장에서 자세히 다룬다.
랭서브(LangServe). 랭체인의 체인을 REST API로 손쉽게 배포하도록 돕는 패키지다. REST API는 웹에서 애플리케이션들이 서로 데이터를 주고받게 하는 시스템으로, 랭서브를 쓰면 프로덕션 환경에 맞는 API를 간단히 설정·운영할 수 있다.
랭스미스(LangSmith). 대규모 언어 모델 애플리케이션을 디버깅·테스트·평가·모니터링할 수 있는 개발자 플랫폼이다. 애플리케이션의 실행 흐름을 나타내는 추적 횟수(Trace Count)와 실제 모델 호출 횟수(LLM Call Count) 같은 지표를 추적해, 예를 들어 추적 횟수가 모델 호출 횟수보다 훨씬 높다면 많은 처리가 모델 호출 없이 이루어지고 있음을 알 수 있다.
1.2 랭체인 버전별 기능 업데이트
2024년 9월 16일 발표된 랭체인 0.3 버전은 안정성과 여러 기능 향상, 커뮤니티 피드백을 반영한 변화를 담았다. 0.1 버전부터의 변화를 정리하면 다음과 같다.
| 버전 | 변화 내용 |
|---|---|
| 0.1.0 | langchain-core·langchain·langchain-community·파트너 패키지로 분리. 패키지 구조 분리로 운영 환경에서의 사용성 개선 |
| 0.1.x | 이벤트 스트리밍 API로 스트리밍 지원 강화, 표준화된 도구 호출 지원, 출력을 구조화하기 위한 표준 인터페이스, @chain 데코레이터 추가 |
| 0.2.0 | langchain이 langchain-community에 대한 의존성 제거(패키지가 더 가벼워짐), 통합 파트너 패키지 확장(특정 통합에 문제가 생겨도 나머지 코드 유지보수가 쉬워짐) |
| 0.3.0 | 내부적으로 Pydantic 1에서 Pydantic 2로 전환, Python 3.8 지원 종료(호환성 문제 방지를 위해 버전 설치 시 주의 필요) |
원문은 이후 발전 계획으로 랭그래프 기능 확장(에이전트 아키텍처의 주요 프레임워크로 발전), 벡터 스토어 업그레이드(추상화 기능 개선), 문서화 개선(버전 관리된 문서 제공)을 꼽았다 — 책 집필 이후 실제로 어떻게 바뀌었는지는 이 장 끝 "최신 동향"에서 다룬다.
1.3 왜 랭체인을 사용해야 하는가
오픈AI API로도 AI 애플리케이션을 충분히 개발할 수 있지만, 랭체인은 모든 기능을 모듈 단위로 나눠 제공하므로 필요한 기능을 쉽게 추가·교체할 수 있다. 데이터베이스 연결, 외부 API 호출, 데이터 처리 같은 기능을 독립적인 모듈로 만들어 조합해서 쓸 수 있어, 나중에 특정 기능만 수정·확장하기 쉽다. 또한 다양한 외부 시스템과 연동할 수 있는 표준 인터페이스를 제공해, 다른 AI 모델로 바꾸거나 새로운 데이터베이스·API를 사용할 때도 기존 코드를 크게 수정할 필요 없이 설정만 바꾸면 된다.
랭체인의 장점을 정리하면 다음과 같다.
- 모듈성(Modularity) — 모든 기능을 독립적인 모듈로 제공한다. 각 모듈은 단독으로도, 다른 모듈과 조합해서도 쓸 수 있어 애플리케이션 구조를 효율적으로 설계할 수 있다.
- 통합의 용이성(Ease of Integration) — 다양한 외부 시스템과 쉽게 연동한다. 오픈AI API를 미스트랄AI나 제미나이로 교체하거나 새 데이터베이스·API를 연결할 때도 설정만 변경하면 된다.
- 확장된 기능(Enhanced Capabilities) — 랭체인 표현 언어(LCEL)와 러너블 인터페이스를 사용하면 복잡한 워크플로를 손쉽게 작성하고 효율적으로 실행할 수 있다.
- 커뮤니티와 지원(Community and Support) — 활성화된 커뮤니티가 다양한 예제와 문서를 제공하며, 지속적인 업데이트로 새 기능이 추가된다.
랭체인과 오픈AI API를 비교하면 다음과 같다(§2.1의 코드 비교에서 이 차이를 직접 확인한다).
| 구분 | 오픈AI API | 랭체인 |
|---|---|---|
| 구조 | 단순한 호출 방식 | 모듈화된 체인 구조 |
| 유연성 | 낮음 — 특정 모델에 종속 | 높음 — 다양한 모델 전환·기능 확장 가능 |
| 코드 복잡도 | 간단함 — 코드 길이 짧음 | 체계적이지만 다소 길어질 수 있음 |
| 재사용성 | 낮음 — 코드 일부를 수정해야 재사용 가능 | 높음 — 모듈화된 구성으로 손쉽게 재사용 가능 |
| 사용 사례 | 간단한 작업 | 복잡하고 확장 가능한 작업 |
| 모델 전환 용이성 | 제한적 — 코드 수정 필요 | 매우 용이 — 모델 클래스만 바꾸면 전환 가능 |
결론적으로 랭체인은 복잡한 애플리케이션 개발과 다양한 모델 통합에 적합하고, 오픈AI API는 간단한 모델 활용에 적합하다.
1.4 랭체인의 주요 활용 사례
랭체인은 대규모 언어 모델을 활용한 응용 프로그램 개발 프레임워크로, 데이터 검색·분석·처리 작업에 특히 유용하다. 이 책에서 다루는 주요 활용 사례는 다음과 같다.
- 검색 증강 생성과 질의응답 시스템(2장) — 외부 데이터베이스나 문서에서 정보를 검색해 응답을 생성하는 방식. 방대한 문서에서 필요한 정보를 찾아 정확한 답변을 제공한다.
- 구조화된 출력 추출(1, 2, 4장) — 데이터를 텍스트가 아니라 JSON·XML 같은 구조화된 형식으로 추출하면 더 효율적이다. 대규모 데이터 처리와 분석 작업에 유용하다.
- 챗봇 구축(2, 5장) — 사용자와 상호작용하는 대화형 AI. 고객 지원, 정보 제공, 간단한 업무 자동화 등에 쓰인다.
- 도구 사용 및 에이전트(6, 7장) — 외부 도구와 API를 연동해 기능을 확장한다. 대규모 언어 모델이 스스로 판단해 데이터를 처리하고 도구를 사용하는 에이전트를 구현할 수 있다(예: 날씨 API 호출, 데이터베이스 조회 자동화).
2. 대규모 언어 모델
대규모 언어 모델은 랭체인에서 매우 중요한 역할을 하는 구성요소로, 사용자가 입력한 텍스트를 바탕으로 새로운 텍스트를 생성하는 인공지능 모델이다. 랭체인은 자체적으로 대규모 언어 모델을 제공하지는 않지만, 오픈AI·코히어·허깅페이스 같은 여러 제공자와 쉽게 상호작용할 수 있는 표준화된 인터페이스를 제공한다.
2.1 랭체인 vs 오픈AI API
대규모 언어 모델을 활용하는 방법은 크게 모델 제공자의 API를 직접 사용하는 방법과 랭체인을 통해 사용하는 방법 두 가지다. 랭체인을 쓰면 프롬프트 템플릿·메모리 관리·도구 호출 등을 이용해 복잡한 애플리케이션을 효율적으로 구축할 수 있고, 특히 모델 간 전환이 쉬워 코드 수정 없이 다양한 모델을 활용할 수 있다. 반면 오픈AI API는 대규모 언어 모델 기능을 직접 쓸 수 있는 간단한 인터페이스를 제공하지만, 모듈화나 유연성이 부족해 각 API에 맞는 코드를 별도로 작성해야 한다.
먼저 오픈AI API를 직접 활용해 응답 생성 파이프라인을 구축해 보자.
# 라이브러리 설치
!pip install langchain_core langchain_openai
# 라이브러리 불러오기
import openai
from typing import List
# 기본 오픈AI 클라이언트 사용
client = openai.OpenAI()
# "안녕하세요!" 메시지를 보내고 응답을 받음
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "안녕하세요!"}]
)
response.choices[0].message.content
client.chat.completions.create()는 오픈AI의 챗 API를 사용해 대화를 생성한다. model은 사용할 모델을, messages는 역할(role)과 내용(content)으로 구성된 메시지 리스트를 지정한다. 응답은 response.choices[0].message.content로 꺼낼 수 있다. 이제 주제를 입력받아 설명을 요청하는 프롬프트 템플릿과, 그 템플릿을 채워 모델에 보내고 응답을 반환하는 함수를 만들어 보자.
# 요청에 사용할 프롬프트 템플릿 정의
prompt_template = "주제 {topic}에 대해 짧은 설명을 해주세요."
# 메시지를 보내고 모델의 응답을 받는 함수
def call_chat_model(messages: List[dict]):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
)
return response.choices[0].message.content
# 주어진 주제에 따라 설명을 요청하는 함수
def invoke_chain(topic: str):
prompt_value = prompt_template.format(topic=topic)
messages = [{"role": "user", "content": prompt_value}]
return call_chat_model(messages)
# "더블딥" 주제로 설명 요청
invoke_chain("더블딥")
이 방식은 API를 직접 호출하는 단순한 구조로 코드가 직관적이지만, 특정 모델(GPT 계열)에 종속적이라 다른 모델로 전환하거나 새 기능을 추가하려면 코드 여러 부분을 고쳐야 한다. 간단한 작업이나 특정 모델을 쓰는 간단한 애플리케이션에는 적합하지만, 확장에는 불리하다.
이번에는 랭체인 프레임워크로 같은 작업을 GPT-4o 모델로 구현해 보자. 랭체인은 모듈화된 접근 방식을 제공해 다양한 구성요소를 손쉽게 조합할 수 있다.
# 라이브러리 불러오기
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv
# 미스트랄AI 모델을 사용할 경우 주석 해제
# from langchain_mistralai.chat_models import ChatMistralAI
# 주어진 주제에 대해 짧은 설명을 요청하는 프롬프트 템플릿 정의
prompt = ChatPromptTemplate.from_template(
"주제 {topic}에 대해 짧은 설명을 해주세요."
)
# 출력 파서를 문자열로 설정
output_parser = StrOutputParser()
# 오픈AI의 gpt-4o 모델을 사용하여 채팅 모델 설정
model = ChatOpenAI(model="gpt-4o")
# 미스트랄AI 모델을 사용할 경우 주석 해제
# model = ChatMistralAI(api_key=MISTRAL_API_KEY)
# 파이프라인 설정: 주제를 받아 프롬프트를 생성하고, 모델로 응답을 생성한 후 문자열로 파싱
chain = prompt | model | output_parser
# "더블딥" 주제로 설명 요청
chain.invoke("더블딥")
ChatOpenAI(model="gpt-4o")가 오픈AI 모델을 GPT-4o로 지정하고, 주석 처리된 줄을 활성화하면 ChatMistralAI 같은 다른 모델로 손쉽게 바꿀 수 있다 — 이렇게 모델을 쉽게 전환할 수 있는 점이 랭체인의 큰 장점이다. chain = prompt | model | output_parser처럼 파이프 연산자(|)로 프롬프트·모델·출력 파서를 하나로 연결하면, 이 파이프라인은 주제를 그대로 통과시키고 → 프롬프트 템플릿을 적용하고 → 모델로 응답을 생성하고 → 응답을 문자열로 파싱하는 네 단계를 자동으로 처리한다. 오픈AI API를 직접 쓴 코드와 비교하면, 함수를 직접 정의하는 대신 파이프 연산자로 단계를 선언적으로 나열했다는 점이 가장 큰 차이다 — 이 방식은 §3에서 다루는 랭체인 표현 언어(LCEL)의 핵심이다.
2.2 대규모 언어 모델 파라미터 설정
대규모 언어 모델에는 조정할 수 있는 기본 하이퍼파라미터가 있다. 이 값들은 모델이 생성하는 텍스트의 스타일·길이·정확도에 영향을 주며, 이를 통해 출력을 조정하고 최적화할 수 있다. 일반적으로 적용되는 주요 하이퍼파라미터는 다음과 같다.
| 파라미터 | 설명 |
|---|---|
| 온도(Temperature) | 생성 텍스트의 다양성을 조정. 0~1 사이 값으로, 작을수록 예측 가능하고 일관된 출력을, 클수록 다양하고 창의적인 출력을 만든다 |
| 최대 토큰 수(Max Tokens) | 생성할 최대 토큰 수를 지정해 텍스트 길이를 제한한다 |
| 최상위 P(Top P) | 특정 확률 분포 내에서 상위 P%의 토큰만 고려하는 방식. 출력의 다양성을 조정하는 데 도움이 된다 |
| 빈도 패널티(Frequency Penalty) | 0~1 사이 값. 값이 클수록 이미 등장한 단어·구절이 다시 등장할 확률을 감소시켜 반복을 줄인다 |
| 존재 패널티(Presence Penalty) | 0~1 사이 값. 값이 클수록 아직 등장하지 않은 새로운 단어의 사용을 장려한다 |
| 정지 시퀀스(Stop Sequences) | 특정 단어·구절이 등장하면 생성을 멈추도록 설정해, 출력을 특정 지점에서 종료한다 |
이 중 temperature와 max_tokens를 설정한 코드 예는 다음과 같다.
from langchain_openai import OpenAI
# LLM 모델 초기화(파라미터 설정)
llm = OpenAI(
temperature=0.7, # 온도 설정(0에서 1 사이의 값)
max_tokens=100, # 최대 토큰 수 설정
model_name="text-davinci-002", # 사용할 모델 지정
)
참고 — 원문은 파라미터 설정 문법을 보여 주는 예로
text-davinci-002를 썼다. 이 모델은 2025년 이후 오픈AI API에서 완전히 폐기(deprecated)되었으므로, 실제 코드에서는 현재 지원되는 채팅 모델(예:gpt-4o-mini)과ChatOpenAI를 대신 사용해야 한다. 파라미터 이름과 의미 자체는 그대로 유효하다.
2.3 랭체인에서 사용할 수 있는 주요 대규모 언어 모델
다음 표는 랭체인에서 쓸 수 있는 주요 대규모 언어 모델을 공급자별로 정리한 것이다(원문 표 1-3, 2025-03-23 기준 스냅샷). 문맥 크기, 백만 토큰당 입력·출력 비용, 한국어 성능 순위를 통해 비용 대비 성능을 비교하고 자신에게 맞는 모델을 선택할 수 있다. 한국어 성능 순위는 한국어 대규모 언어 모델 전용 대시보드(LogicKor)의 데이터를 기반으로 한 대략적인 순위다.
| 공급자 | 모델 | 문맥 크기 | 입력 비용(백만 토큰당) | 출력 비용(백만 토큰당) | 한국어 순위 |
|---|---|---|---|---|---|
| 오픈AI | GPT-4.5-preview-2025-02-27 | 128k | $75.00(캐시 $37.50) | $150.00 | - |
| 오픈AI | GPT-4o-2024-08-09 | 128k | $2.50(캐시 $1.25) | $10.00 | - |
| 오픈AI | GPT-4o-mini-2024-07-18 | 128k | $0.15(캐시 $0.075) | $0.60 | 1 |
| 오픈AI | o1-2024-12-17 | 200k | $15.00(캐시 $7.50) | $60.00 | - |
| 오픈AI | o1-mini-2024-09-12 | 200k | $1.10(캐시 $0.55) | $4.40 | - |
| 앤트로픽 | Claude 3.5 Sonnet | 200k | $3.00 | $15.00 | - |
| 앤트로픽 | Claude 3 Opus | 200k | $15.00 | $75.00 | 2 |
| 앤트로픽 | Claude 3.5 Haiku | 200k | $0.80 | $4.00 | - |
| 구글 | Gemini 1.5 Pro | 128k | $1.25(캐시 $0.3125) | $5.00 | - |
| 구글 | Gemini 2.0 Flash | 1M | $0.10 | $0.40 | 3 |
| 구글 | Gemini 2.0 Flash-Lite | 1M | $0.075(캐시 $0.01875) | $0.30 | - |
| 미스트랄AI | mistral-large | 128k | $2.00 | $6.00 | 4 |
| 딥시크 | deepseek-chat | 64k | $0.07 | $1.10 | - |
| 딥시크 | deepseek-reasoner | 64k | $0.14 | $2.19 | - |
참고 — 이 표는 추출 손상으로 일부 옮기지 않았다. 앤트로픽 3개 모델·구글 Gemini 2.0 Flash·딥시크 2개 모델의 "캐시" 괄호 값은 원문 추출 과정에서 뒤섞여, 캐시 단가가 기본 단가보다 오히려 높게 나타났다(예: 정상적인 캐시 할인이라면 더 싸야 하는데 원문 추출값은 그 반대였다). 그럴듯하게 숫자를 지어내는 대신 이 여섯 자리는 표에서 뺐다. 기본 입력·출력 단가, 문맥 크기, 한국어 순위는 반복된 두 추출 결과가 서로 일치해 신뢰도가 높다.
한국어 성능 순위 1~4위였던 모델을 포함해 이 표에 실린 여러 모델의 가격·지원 상태는 책 집필 이후 크게 바뀌었다 — 자세한 내용은 이 장 끝의 "최신 동향"에서 다룬다. 딥시크 모델은 중국에서 개발된 모델로, 모델 크기 대비 비용이 저렴해 테스트 용도로 쓰기에 괜찮다고 원문은 설명한다.
3. 랭체인 표현 언어
랭체인 표현 언어(LCEL)는 랭체인의 여러 구성요소를 체인 형태로 연결할 수 있게 하는 선언적 방식의 언어다. 선언적 방식이란 "어떻게"가 아니라 "무엇을" 할지를 명확하게 기술하는 방식으로, 복잡한 과정을 간단하게 표현할 수 있다. LCEL은 프로토타입에서 실제 운영 단계까지 코드 수정 없이 일관되게 쓸 수 있도록 설계되어, 간단한 '프롬프트 + 모델' 체인부터 수백 단계로 이루어진 복잡한 작업 흐름까지 안정적으로 실행할 수 있다.
랭체인의 구성요소는 러너블이라는 개념으로 추상화되며, 러너블은 체인의 각 단계에서 실행 가능한 작업을 수행하는 핵심 요소다. LCEL에는 여러 유용한 기능이 있다 — 모델 출력을 실시간으로 스트리밍하고, 동기·비동기 API를 모두 지원해 프로토타입과 운영 환경에서 일관된 성능을 유지하며, 여러 작업을 병렬로 처리해 속도를 높이고, 실패한 작업은 자동으로 재시도하거나 대체 경로를 선택할 수 있다. 모든 입력·출력은 Pydantic과 JSON Schema로 자동 스키마가 생성되어 데이터를 안전하게 검사할 수 있고, 랭스미스와 통합되어 모든 작업 단계가 자동으로 기록되므로 체인의 동작을 쉽게 모니터링·분석할 수 있다.
3.1 러너블 표준 인터페이스
러너블은 여러 공통 메서드를 제공하는 표준 인터페이스를 사용한다. 덕분에 대규모 언어 모델·출력 파서·프롬프트 템플릿처럼 서로 다른 기능을 가진 구성요소를 모두 동일한 방식으로 호출하고 결과를 처리할 수 있다.
| 메서드 | 역할 |
|---|---|
invoke() |
단일 입력을 처리하여 결과를 반환하는 동기 메서드 |
batch() |
여러 입력을 동시에 처리하는 동기 메서드 |
stream() |
결과를 스트리밍 방식으로 반환하는 동기 메서드 |
ainvoke() |
invoke()의 비동기 버전 |
abatch() |
batch()의 비동기 버전 |
astream() |
stream()의 비동기 버전 |
astream_log() |
중간 단계와 최종 결과를 비동기적으로 스트리밍 |
astream_events() |
체인에서 발생하는 이벤트를 비동기적으로 스트리밍 |
다음은 주어진 주제에 대해 GPT 모델로 짧은 설명을 요청하는 체인을 세 가지 방식으로 호출하는 예다.
# 라이브러리 불러오기
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 오픈AI의 대규모 언어 모델 설정
model = ChatOpenAI(model="gpt-4o-mini")
# 프롬프트 템플릿 정의: 주어진 주제에 대한 설명 요청
prompt = ChatPromptTemplate.from_template("주제 {topic}에 대해 짧은 설명을 해주세요.")
# 출력 파서 정의: AI 메시지의 출력 내용을 추출
parser = StrOutputParser()
# 프롬프트, 모델, 출력 파서를 체인으로 연결
chain = prompt | model | parser
# 단일 입력 처리
chain.invoke({"topic": "더블딥"})
# 여러 주제를 한 번에 처리 — 성능 최적화나 동시 처리에 유용
chain.batch([{"topic": "더블딥"}, {"topic": "인플레이션"}])
# 응답을 토큰 단위로 실시간 스트리밍
for token in chain.stream({"topic": "더블딥"}):
print(token, end="", flush=True)
invoke()는 하나의 주제에 대해 동기식으로 응답을 처리한다. batch()는 "더블딥"과 "인플레이션" 두 주제를 한 번에 처리해, 여러 입력을 동시에 처리해야 할 때 유용하다. stream()은 모델이 응답을 완전히 생성하기 전에 토큰 단위로 실시간 출력을 받아, 대기 시간이 중요한 응용 프로그램에서 유용하다. flush=True는 출력 버퍼를 즉시 비워 지연 없이 화면에 보여 준다.
3.2 러너블을 체인으로 연결하는 방법
파이프 연산자. 랭체인에서는 프롬프트와 모델이 모두 러너블로 작동하며, 프롬프트 호출의 출력 타입이 챗 모델의 입력 타입과 같아서 chain = prompt | model | StrOutputParser()처럼 파이프 연산자(|)로 여러 러너블을 체인으로 연결할 수 있다. 체인을 더 복잡하게 구성해 다른 러너블과 결합할 수도 있다 — 예를 들어 생성된 설명을 영어로 번역하는 체인을 추가로 연결해 보자.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# "이 대답을 영어로 번역해 주세요"라는 요청을 생성하는 프롬프트 템플릿 정의
analysis_prompt = ChatPromptTemplate.from_template(
"이 대답을 영어로 번역해 주세요: {answer}"
)
# 이전에 정의된 체인(chain)과 새로운 작업을 연결하는 체인 구성
composed_chain = {"answer": chain} | analysis_prompt | model | StrOutputParser()
# "더블딥"이라는 주제로 응답을 생성하고 체인 실행
composed_chain.invoke({"topic": "더블딥"})
{"answer": chain}은 앞서 정의된 chain이 만든 응답을 "answer"라는 키에 담아 다음 단계(analysis_prompt)로 넘긴다. 이렇게 체인 두 개를 이어 붙이면, 주제에 대한 설명이 먼저 생성되고 그 설명이 영어로 번역된 최종 결과가 자동으로 순차 실행되어 반환된다.
랭체인에서는 함수도 러너블로 강제 변환(coerce)할 수 있어 체인에 사용자 정의 로직을 추가할 수 있다. 람다 함수로 입력 데이터를 다른 형식으로 변환한 뒤 체인에 연결하는 예다.
# 람다 함수를 사용한 체인 구성
composed_chain_with_lambda = (
chain # 이전에 정의된 체인으로 주제에 대한 답변을 생성
| (lambda input: {"answer": input}) # 답변을 "answer" 키를 가진 딕셔너리로 변환
| analysis_prompt # "answer" 값을 영어로 번역하도록 프롬프트에 전달
| model # 모델을 사용해 번역된 응답 생성
| StrOutputParser() # 결과를 문자열로 파싱
)
composed_chain_with_lambda.invoke({"topic": "더블딥"})
다만 이렇게 람다 함수로 입력을 변환하는 방식은 스트리밍 작업과 호환되지 않을 수 있으므로 주의해야 한다.
참고 — 파이썬에서 파이프 연산자를 오버로딩하는 방법. 파이썬은 기본적으로 랭체인 같은 파이프 연산자를 제공하지 않지만,
__or__라는 특별한 메서드로|연산자의 동작을 사용자 정의로 바꿀(오버로딩) 수 있다. 다음은 문자열에 느낌표를 추가한 뒤 뒤집는 과정을 파이프 연산자로 연결하는 예다.
class CustomLCEL:
def __init__(self, value):
self.value = value # 객체 생성 시 값을 초기화
def __or__(self, other):
if callable(other):
# other가 함수이면 함수를 호출하고 그 결과를 새로운 객체로 반환
return CustomLCEL(other(self.value))
else:
# other가 함수가 아니면 오류를 발생시킴
raise ValueError("Right operand must be callable")
def result(self):
return self.value # 현재 값을 반환
# 문자열 끝에 느낌표를 추가하는 함수
def add_exclamation(s):
return s + "!"
# 문자열을 뒤집는 함수
def reverse_string(s):
return s[::-1]
# 파이프라인을 생성하여 순차적으로 문자열 변환 작업을 수행
custom_chain = (
CustomLCEL("랭체인 공부하기") # "랭체인 공부하기"로 초기화된 객체 생성
| add_exclamation # 느낌표 추가
| reverse_string # 문자열 뒤집기
)
result = custom_chain.result()
print(result)
출력:
!기하부공 인체랭
CustomLCEL 클래스는 __or__ 메서드를 정의해 | 연산자가 호출될 때마다 오른쪽 피연산자(함수)를 왼쪽 값에 적용하고, 그 결과를 다시 CustomLCEL 객체로 감싸 반환한다. 이 덕분에 객체 | 함수1 | 함수2 형태로 여러 함수를 순차 연결할 수 있다 — 랭체인의 prompt | model | parser도 근본적으로 같은 원리로 동작한다.
파이프 메서드. 파이프 연산자 대신 .pipe() 메서드로도 러너블을 순차 연결할 수 있다.
# 여러 작업을 순차적으로 .pipe()로 연결하여 체인 구성하기
composed_chain_with_pipe = (
chain
.pipe(lambda input: {"answer": input})
.pipe(analysis_prompt)
.pipe(model)
.pipe(StrOutputParser())
)
composed_chain_with_pipe.invoke({"topic": "더블딥"})
# 여러 모듈을 한 번에 연결하는 더 간단한 방법
composed_chain_with_pipe = chain.pipe(
lambda input: {"answer": input}, analysis_prompt, model, StrOutputParser()
)
composed_chain_with_pipe.invoke({"topic": "더블딥"})
RunnableParallel을 이용한 체인 구성. RunnableParallel은 여러 체인을 병렬로 실행해 효율성을 높이는 데 유용하다. 다음은 같은 주제에 대해 한국어와 영어 설명을 동시에 생성하는 예다.
from langchain_core.runnables import RunnableParallel
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
model = ChatOpenAI()
# 한국어 설명 생성 프롬프트 체인
kor_chain = (
ChatPromptTemplate.from_template("{topic}에 대해 짧은 설명을 해주세요.")
| model
| StrOutputParser()
)
# 영어 설명 생성 프롬프트 체인
eng_chain = (
ChatPromptTemplate.from_template("{topic}에 대해 짧게 영어로 설명을 해주세요.")
| model
| StrOutputParser()
)
# 병렬 실행을 위한 RunnableParallel 설정
parallel_chain = RunnableParallel(kor=kor_chain, eng=eng_chain)
# 주제에 대한 한국어와 영어 설명 생성
result = parallel_chain.invoke({"topic": "더블딥"})
print("한글 설명:", result["kor"])
print("영어 설명:", result["eng"])
두 체인은 RunnableParallel로 병렬 실행되도록 설정되며, invoke()로 "더블딥"이라는 주제를 한 번 전달하면 한국어·영어 설명이 동시에 생성되어 각각 "kor"·"eng" 키에 저장된다.
4. 프롬프트
프롬프트 템플릿은 사용자의 입력과 매개변수를 언어 모델에 대한 지침으로 변환하는 역할을 한다. 이를 통해 모델이 상황을 효과적으로 파악하고 관련성 높고 일관된 텍스트 출력을 생성하도록 유도할 수 있다. 프롬프트 템플릿의 입력은 딕셔너리 형태로 받으며, 각 키는 프롬프트 내의 변수를 나타내고 나중에 실제 값으로 채워진다.
4.1 문자열 프롬프트 템플릿
문자열 프롬프트 템플릿(String PromptTemplate)은 단일 문자열 형태의 프롬프트를 생성하며, 일반적으로 간단한 입력에 사용한다.
from langchain_core.prompts import PromptTemplate
# 주어진 주제에 대한 조언을 요청하는 프롬프트 템플릿 정의
prompt_template = PromptTemplate.from_template(
"주제 {topic}에 대해 금융 관련 짧은 조언을 해주세요"
)
# '투자' 주제로 프롬프트 템플릿 호출
prompt_template.invoke({"topic": "투자"})
출력:
StringPromptValue(text='주제 투자에 대해 금융 관련 짧은 조언을 해주세요')
from_template()으로 템플릿을 만들고 {topic}이라는 변수를 넣어 두면, invoke()로 실제 주제('투자')를 전달했을 때 그 변수 자리가 채워진 완성된 문자열이 반환된다.
4.2 챗 프롬프트 템플릿
ChatPromptTemplate은 대화형 AI 모델과 상호작용하는 데 필요한 메시지 시퀀스(대화 흐름을 구성하는 메시지들의 연속된 집합)를 생성하는 구조다. 각 메시지는 시스템·사용자·AI 역할로 구성할 수 있다.
from langchain_core.prompts import ChatPromptTemplate
# 챗 프롬프트 템플릿 정의: 사용자와 시스템 간의 메시지 포함
prompt_template = ChatPromptTemplate.from_messages([
("system", "당신은 유능한 금융 조언가입니다."),
("user", "주제 {topic}에 대해 금융 관련 조언을 해주세요"),
])
# '주식' 주제로 챗 프롬프트 템플릿 호출
prompt_template.invoke({"topic": "주식"})
출력:
ChatPromptValue(messages=[SystemMessage(content='당신은 유능한 금융 조언가입니다.'),
HumanMessage(content='주제 주식에 대해 금융 관련 조언을 해주세요')])
시스템 메시지("당신은 유능한 금융 조언가입니다.")는 AI의 역할을 정의하고, 사용자 메시지는 {topic}을 실제 값('주식')으로 채운 형식으로 생성된다.
4.3 메시지 자리 표시자
메시지 자리 표시자(MessagesPlaceholder)는 템플릿 안에서 동적으로 메시지를 삽입할 자리를 예약한다. 사용자가 전달한 메시지 목록을 특정 위치에 삽입하고 싶을 때 쓴다.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage
# (방법1) MessagesPlaceholder 클래스로 자리 표시자 지정
prompt_template = ChatPromptTemplate.from_messages([
("system", "당신은 유능한 금융 조언가입니다."),
MessagesPlaceholder("msgs"),
])
prompt_template.invoke({"msgs": [HumanMessage(content="안녕하세요!")]})
# (방법2) 문자열 "placeholder"와 변수만으로 같은 작업 수행
prompt_template = ChatPromptTemplate.from_messages([
("system", "당신은 유능한 금융 조언가입니다."),
("placeholder", "{msgs}"), # 여기서 'msgs'가 자리 표시자로 사용됨
])
prompt_template.invoke({"msgs": [HumanMessage(content="안녕하세요!")]})
두 방법 모두 결과는 같다 — invoke()로 전달한 메시지 리스트가 자리 표시자에 그대로 삽입된다. 방법2는 클래스를 명시적으로 쓰지 않아 더 간결하지만 동일한 역할을 한다.
4.4 퓨샷 프롬프트
퓨샷 프롬프트(few-shot prompt)는 대규모 언어 모델이 더 나은 성능을 내도록 몇 가지 예제 입력과 출력을 제공하는 방식이다. 예제가 전혀 없으면 제로샷(zero-shot), 한 개면 원샷(one-shot), n개면 n샷(퓨샷)이라 부른다.
먼저 질문·답변을 포맷하는 예제 프롬프트를 만들고, 퓨샷 예제 목록을 준비한다.
from langchain_core.prompts import PromptTemplate
# 질문과 답변을 포맷하는 프롬프트 템플릿 정의
example_prompt = PromptTemplate.from_template("질문: {question}\n답변: {answer}")
# 퓨샷 예제 목록 생성
examples = [
{
"question": "주식 투자와 예금 중 어느 것이 더 수익률이 높은가?",
"answer": (
"후속 질문이 필요한가요: 네.\n"
"후속 질문: 주식 투자의 평균 수익률은 얼마인가요?\n"
"중간 답변: 주식 투자의 평균 수익률은 연 7%입니다.\n"
"후속 질문: 예금의 평균 이자율은 얼마인가요?\n"
"중간 답변: 예금의 평균 이자율은 연 1%입니다.\n"
"따라서 최종 답변은: 주식 투자"
),
},
{
"question": "부동산과 채권 중 어느 것이 더 안정적인 투자처인가?",
"answer": (
"후속 질문이 필요한가요: 네.\n"
"후속 질문: 부동산 투자의 위험도는 어느 정도인가요?\n"
"중간 답변: 부동산 투자의 위험도는 중간 수준입니다.\n"
"후속 질문: 채권의 위험도는 어느 정도인가요?\n"
"중간 답변: 채권의 위험도는 낮은 편입니다.\n"
"따라서 최종 답변은: 채권"
),
},
]
각 예제는 단계별 추론(후속 질문 → 중간 답변 → 최종 답변)까지 포함한 답변 형식을 보여 준다. FewShotPromptTemplate으로 이 예제들을 한꺼번에 프롬프트에 넣으면, 새 질문을 던졌을 때 모델이 예제 형식을 참고하게 만들 수 있다.
from langchain_core.prompts import FewShotPromptTemplate
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="질문: {input}",
input_variables=["input"],
)
print(prompt.invoke({"input": "부동산 투자의 장점은 무엇인가?"}).to_string())
모든 예제를 한꺼번에 쓰지 않고, 입력과 가장 유사한 예제만 골라 쓸 수도 있다 — 이때 예제 선택기를 쓴다. SemanticSimilarityExampleSelector는 입력된 질문과 예제 사이의 유사도를 계산해 가장 비슷한 예제를 찾아 준다(이 밖에 BaseExampleSelector·LengthBasedExampleSelector 등도 있다).
from langchain_chroma import Chroma
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings
# 예제 선택기 초기화
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples, # 사용할 예제 목록
OpenAIEmbeddings(api_key=api_key), # 임베딩 생성에 사용하는 클래스
Chroma, # 임베딩 저장·유사도 검색을 수행하는 벡터 저장소 클래스
k=1, # 선택할 예제의 수
)
# 입력과 가장 유사한 예제 선택
question = "부동산 투자의 장점은 무엇인가?"
selected_examples = example_selector.select_examples({"question": question})
k=1은 입력 질문과 가장 유사한 예제 하나만 선택하겠다는 의미다("부동산과 채권" 예제가 선택된다 — 둘 다 부동산을 다루기 때문이다). 다음은 퓨샷 프롬프팅을 예제 선택기·실제 모델과 함께 쓰는 완결된 예다.
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import ChatOpenAI
example_prompt = PromptTemplate(
input_variables=["question", "answer"],
template="질문: {question}\n답변: {answer}",
)
# 퓨샷 프롬프트 템플릿 설정
prompt = FewShotPromptTemplate(
example_selector=example_selector, # 입력과 가장 관련 있는 예제를 선택
example_prompt=example_prompt, # 질문·답변 형식으로 예제를 제공
prefix="다음은 금융 관련 질문과 답변의 예입니다:",
suffix="질문: {input}\n답변:",
input_variables=["input"],
)
model = ChatOpenAI(model_name="gpt-4o")
chain = prompt | model
response = chain.invoke({"input": "부동산 투자의 장점은 무엇인가?"})
print(response.content)
출력 결과, GPT-4o 모델이 예제를 참고해 '부동산 투자의 장점'에 대해 적절한 답변을 생성한다 — 이 방식은 AI 모델이 특정 도메인·스타일의 답변을 생성하는 데 도움을 준다.
4.5 프롬프트 허브
프롬프트 허브(랭체인 허브)는 랭체인 생태계에서 프롬프트를 쉽게 공유하고 재사용할 수 있도록 지원하는 중앙 저장소다. 자신이 만든 프롬프트를 다른 개발자와 공유하거나, 커뮤니티가 제공하는 프롬프트를 검색해 프로젝트에 적용할 수 있다. 또한 프롬프트의 여러 버전을 관리해, 최신 버전뿐 아니라 필요에 따라 과거의 특정 버전을 선택해 쓸 수도 있다.
from langchain import hub
# 최신 버전의 프롬프트 불러오기
prompt = hub.pull("hardkothari/prompt-maker")
# 특정 버전의 프롬프트 불러오기
hub.pull("hardkothari/prompt-maker:c5db8eee")
hub.pull("hardkothari/prompt-maker")는 hardkothari(작성자)가 올린 prompt-maker라는 프롬프트를 허브에서 가져온다. 버전을 명시하고 싶으면 :c5db8eee처럼 커밋 해시를 붙이면 되고, 버전 정보는 허브 사이트의 커밋(Commits) 페이지에서 확인할 수 있다. 원문이 쓴 smith.langchain.com/hub라는 주소와 hub.pull() 함수 자체는 책 집필 이후 크게 바뀌었다 — 자세한 내용은 이 장 끝 "최신 동향"에서 다룬다.
5. 출력 파서
출력 파서(OutputParser)는 모델이 생성한 텍스트를 구조화된 형식으로 변환하는 도구다. 단순한 텍스트 출력이 아니라 체계적인 데이터로 변환할 수 있게 해 준다. 최근에는 모델들이 함수(function) 또는 도구 호출(tool calling)을 지원하기 시작하면서 이런 작업을 자동으로 처리하는 경우가 늘고 있으므로, 가능하다면 출력 파서 대신 함수·도구 호출을 쓰기를 원문은 권장한다.
| 이름 | 입력 유형 | 출력 유형 | 설명 |
|---|---|---|---|
| JSON | 문자열 | 메시지 | JSON 객체 | 지정된 JSON 객체 반환. Pydantic 모델 지원 |
| XML | 문자열 | 메시지 | 딕셔너리(dict) | XML 태그의 딕셔너리 반환. XML 출력이 필요할 때 사용 |
| CSV | 문자열 | 메시지 | 문자열 목록(List[str]) | 쉼표로 구분된 값 목록 반환 |
| OutputFixing | 문자열 | 메시지 | - | 다른 출력 파서의 오류 수정 |
| RetryWithError | 문자열 | 메시지 | - | 출력 파서의 오류 수정 및 원본 지시 사항 재전송 |
| Pydantic | 문자열 | 메시지 | Pydantic BaseModel | 사용자 정의 Pydantic 모델 반환 |
| YAML | 문자열 | 메시지 | Pydantic BaseModel | YAML로 인코딩된 Pydantic 모델 반환 |
| PandasDataFrame | 문자열 | 메시지 | 딕셔너리(dict) | Pandas DataFrame 작업 시 유용 |
| Enum | 문자열 | 메시지 | Enum | 제공된 Enum 값 중 하나로 응답 구문 분석 |
| Datetime | 문자열 | 메시지 | datetime.datetime | 응답을 datetime 형식으로 구문 분석 |
| Structured | 문자열 | 메시지 | 딕셔너리(Dict[str, str]) | 문자열 필드만 포함된 구조화된 정보 반환 |
5.1 출력 파서의 세 가지 주요 메서드
포맷 지침 가져오기. 출력 파서는 모델에게 응답을 어떤 형식으로 출력해야 하는지 알려주는 지침을 제공한다. get_format_instructions()로 이 지침을 확인할 수 있다.
from langchain_core.output_parsers import JsonOutputParser
parser = JsonOutputParser()
instructions = parser.get_format_instructions()
print(instructions)
출력:
Return a JSON object.
파싱. parse() 메서드는 모델의 응답을 프로그래밍에서 다루기 쉬운 형태(파이썬 딕셔너리)로 변환한다.
ai_response = '{"이름": "김철수", "나이": 30}'
parsed_response = parser.parse(ai_response)
print(parsed_response)
출력:
{'이름': '김철수', '나이': 30}
프롬프트와 함께 파싱. parse_with_prompt()는 모델의 응답과 함께 원래 질문(프롬프트)까지 받아 분석한다. AI 응답에 오류가 있을 때 문제를 수정하거나 재시도할 때 유용하다.
from langchain.output_parsers import RetryWithErrorOutputParser
from langchain_core.output_parsers import JsonOutputParser
from langchain_openai import ChatOpenAI
# 파서 설정: 오류 발생 시 재시도 기능 + JSON 파싱 + 오류 수정을 요청할 LLM
parser = RetryWithErrorOutputParser.from_llm(
parser=JsonOutputParser(), llm=ChatOpenAI()
)
question = "가장 큰 대륙은?"
ai_response = "아시아입니다." # JSON 형식이 아닌 잘못된 응답
try:
result = parser.parse_with_prompt(ai_response, question)
print(result)
except Exception as e:
print(f"오류 발생: {e}")
# 여기서 AI에게 다시 질문할 수 있다
출력:
오류 발생: 'str' object has no attribute 'to_string'
ai_response가 JSON 형식이 아니므로 파싱에 실패해 예외가 발생한다. ai_response = '{"answer": "아시아"}'처럼 올바른 JSON 형식으로 바꾸면 파서가 문제없이 처리한다.
5.2 PydanticOutputParser
PydanticOutputParser는 대규모 언어 모델이 생성한 자유 형식 텍스트를, 개발자가 정의한 Pydantic 데이터 구조에 맞춰 자동으로 변환하고 그 과정에서 데이터 유효성까지 검증한다. AI가 지정된 형식을 따르지 않으면 오류가 날 수 있으므로 프롬프트 설계가 중요하다.
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field, model_validator
model = ChatOpenAI(model_name="gpt-4o", temperature=0.0)
# 원하는 데이터 구조 정의
class FinancialAdvice(BaseModel):
setup: str = Field(description="금융 조언 상황을 설정하기 위한 질문")
advice: str = Field(description="질문을 해결하기 위한 금융 답변")
# Pydantic을 사용한 사용자 정의 검증 로직
@model_validator(mode="before")
@classmethod
def question_ends_with_question_mark(cls, values: dict) -> dict:
setup = values.get("setup", "")
if not setup.endswith("?"):
raise ValueError("잘못된 질문 형식입니다! 질문은 '?'로 끝나야 합니다.")
return values
# 파서 설정 및 프롬프트 템플릿에 지침 삽입
parser = PydanticOutputParser(pydantic_object=FinancialAdvice)
prompt = PromptTemplate(
template="다음 금융 관련 질문에 답변해 주세요.\n{format_instructions}\n질문: {query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 언어 모델을 사용해 데이터 구조를 채우도록 프롬프트와 모델 설정
chain = prompt | model | parser
try:
result = chain.invoke({"query": "부동산에 관련하여 금융 조언을 받을 수 있게 질문하라."})
print(result)
except Exception as e:
print(f"오류 발생: {e}")
출력:
setup='부동산 투자를 고려하고 있습니다. 현재 시장 상황에서 부동산에 투자하는 것이 좋은
결정일까요?' advice='부동산 투자는 장기적인 관점에서 안정적인 수익을 제공할 수 있지만,
시장의 변동성과 지역별 특성을 고려해야 합니다. 현재 시장 상황, 금리, 지역 개발 계획 등을
분석하여 투자 결정을 내리는 것이 중요합니다. 전문가와 상담하여 구체적인 전략을 세우는 것
도 좋은 방법입니다.'
@model_validator로 정의한 question_ends_with_question_mark()는 AI가 생성한 질문이 물음표로 끝나는지 검증해, 형식을 어기면 ValueError를 일으킨다. 출력 결과를 보면 setup에는 물음표로 끝나는 질문이, advice에는 그에 대한 조언이 각각 구조화되어 담겨 후속 작업이나 데이터 분석에 바로 활용할 수 있다.
5.3 SimpleJsonOutputParser
SimpleJsonOutputParser는 JSON 형식의 출력이 필요하지만 Pydantic 모델 같은 복잡한 구조가 필요하지 않을 때 유용하다. 실시간 처리와 스트리밍을 지원해 모델 출력을 효율적으로 처리할 수 있지만, 모델이 항상 완벽한 JSON을 생성하는 것은 아니므로 오류 처리가 중요하다.
from langchain.output_parsers.json import SimpleJsonOutputParser
json_prompt = PromptTemplate.from_template(
"다음 질문에 대한 답변이 포함된 JSON 객체를 반환하십시오: {question}"
)
json_parser = SimpleJsonOutputParser()
json_chain = json_prompt | model | json_parser
# 스트리밍 예시: 질문에 대한 답변이 점진적으로 구문 분석됨
list(json_chain.stream({"question": "비트코인에 대한 짧은 한 문장 설명."}))
출력(일부):
[{}, {'answer': ''}, {'answer': '비'}, {'answer': '비트'}, {'answer': '비트코'},
{'answer': '비트코인'}, {'answer': '비트코인은'}, {'answer': '비트코인은 분산된 디지털'},
{'answer': '비트코인은 분산된 디지털 화폐'}]
출력은 빈 JSON 객체 {}로 시작해, 'answer' 키의 값이 한 글자씩 늘어나며 채워진다 — 언어 모델이 응답을 어떻게 구성해 나가는지, SimpleJsonOutputParser가 이를 어떻게 실시간으로 파싱하는지 보여 준다.
5.4 JsonOutputParser
JsonOutputParser는 JSON 형식에 특화된 파서로, Pydantic과 함께 쓰면 예상되는 스키마를 간편하게 선언할 수 있다. PydanticOutputParser가 Pydantic 모델로 직접 검증·구조화하는 데 적합하다면, JsonOutputParser는 JSON 데이터 처리 자체에 중점을 두며 부분적으로 생성된 JSON을 스트리밍으로 처리하는 유연성도 제공한다.
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
model = ChatOpenAI(temperature=0)
class FinancialAdvice(BaseModel):
setup: str = Field(description="금융 조언 상황을 설정하기 위한 질문")
advice: str = Field(description="질문을 해결하기 위한 금융 답변")
# JSON 출력 파서 설정 및 프롬프트 템플릿에 지침 삽입
parser = JsonOutputParser(pydantic_object=FinancialAdvice)
prompt = PromptTemplate(
template="다음 금융 관련 질문에 답변해 주세요.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 체인 구성: 프롬프트 -> 모델 -> 파서
chain = prompt | model | parser
chain.invoke({"query": "부동산에 관련하여 금융 조언을 받을 수 있게 질문하라."})
출력:
{'setup': '부동산 투자 시 어떤 금융 상품을 활용하는 것이 좋을까요?',
'advice': '부동산 투자 시에는 주택담보대출, 주택청약종합저축, 부동산투자신탁 등 다양한
금융 상품을 활용할 수 있습니다. 각 상품의 장단점을 고려하여 자신의 상황에 맞는 금융 상품
을 선택하는 것이 중요합니다.'}
6. 메모리 관리: 대화 기록 유지
챗봇을 개발할 때 대화의 흐름을 유지하는 기능은 사용자 경험을 크게 향상시킨다. 대화 이력을 관리하면 사용자가 이전에 어떤 질문을 했는지 기억하고 연관된 답변을 제공해, 더 자연스럽고 맥락을 반영한 대화를 이어갈 수 있다.
6.1 기본적인 대화 이력 전달
챗봇에 메모리를 추가하는 가장 간단한 방법은 이전 대화를 그대로 프롬프트에 전달하는 것이다.
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(model="gpt-4o-mini")
# 프롬프트 템플릿 정의: 금융 상담 역할 + 대화 이력이 들어갈 자리
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 금융 상담사입니다. 사용자에게 최선의 금융 조언을 제공합니다."),
("placeholder", "{messages}"), # 대화 이력 추가
])
chain = prompt | chat
# 이전 대화를 포함한 메시지 전달
ai_msg = chain.invoke({
"messages": [
("human", "저축을 늘리기 위해 무엇을 할 수 있나요?"), # 사용자의 첫 질문
("ai", "저축 목표를 설정하고, 매달 자동 이체로 일정 금액을 저축하세요."), # 챗봇의 답변
("human", "방금 뭐라고 했나요?"), # 사용자의 재확인 질문
]
})
print(ai_msg.content)
첫 번째 질문에 대한 챗봇의 답변까지 포함된 대화 이력을 프롬프트에 통째로 전달하므로, 사용자가 "방금 뭐라고 했나요?"라고 재확인해도 AI는 이전 대화 내용을 기억해 적절히 답한다. 다만 이 방식은 대화가 길어질수록 매번 전체 이력을 직접 구성해 넘겨야 하므로 관리가 번거롭다.
6.2 대화 이력 관리 및 처리
ChatMessageHistory 클래스를 쓰면 대화 내용을 저장하고 재사용하는 등, 대화 이력을 더 체계적으로 관리할 수 있다.
from langchain_community.chat_message_histories import ChatMessageHistory
# 대화 이력 저장을 위한 클래스 초기화
chat_history = ChatMessageHistory()
chat_history.add_user_message("저축을 늘리기 위해 무엇을 할 수 있나요?")
chat_history.add_ai_message("저축 목표를 설정하고, 매달 자동 이체로 일정 금액을 저축하세요.")
# 새로운 질문 추가 후 다시 체인 실행
chat_history.add_user_message("방금 뭐라고 했나요?")
ai_response = chain.invoke({"messages": chat_history.messages})
print(ai_response.content) # 챗봇은 이전 메시지를 기억하여 답변한다
add_user_message()·add_ai_message()로 대화 메시지를 이력에 차곡차곡 쌓아 두고, 다음 질문을 실행할 때는 chat_history.messages를 그대로 체인에 넘긴다 — §6.1처럼 매번 이력을 직접 나열하지 않아도 된다.
6.3 자동 대화 이력 관리
RunnableWithMessageHistory를 쓰면 대화 이력을 완전히 자동으로 관리할 수 있다. 이 클래스는 세션 ID에 따라 이력을 자동으로 저장·불러와, 이전 대화를 AI에게 전달하고 자연스러운 대화를 이어가게 한다.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
# 시스템 메시지와 대화 이력을 사용하는 프롬프트 템플릿 정의
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 금융 상담사입니다. 모든 질문에 최선을 다해 답변하십시오."),
("placeholder", "{chat_history}"), # 이전 대화 이력
("human", "{input}"), # 사용자의 새로운 질문
])
chat_history = ChatMessageHistory()
chain = prompt | chat
# RunnableWithMessageHistory 클래스로 체인을 감싸 대화 이력을 자동 관리
chain_with_message_history = RunnableWithMessageHistory(
chain,
lambda session_id: chat_history, # 세션 ID에 따라 대화 이력을 불러오는 함수
input_messages_key="input",
history_messages_key="chat_history",
)
# 첫 번째 질문
chain_with_message_history.invoke(
{"input": "저축을 늘리기 위해 무엇을 할 수 있나요?"},
{"configurable": {"session_id": "unused"}},
).content
# 이전 응답을 확인하는 두 번째 질문
chain_with_message_history.invoke(
{"input": "내가 방금 뭐라고 했나요?"},
{"configurable": {"session_id": "unused"}},
).content
출력(두 번째 질문):
"저축을 늘리기 위해 무엇을 할 수 있는지 질문하셨습니다."
session_id로 특정 세션의 대화 이력을 추적하고, input_messages_key·history_messages_key는 입력·이력을 처리할 때 쓰는 키를 정의한다. 사용자가 새 질문을 던질 때마다 이전 대화가 체인에 자동으로 기록되어 다음 질문에 그대로 활용된다.
6.4 대화 이력 요약 및 트리밍
대화가 길어지면 모든 메시지를 다 기억하는 것은 비효율적이다 — 이력이 길어질수록 모델이 처리할 정보량이 늘어 응답이 느려지고 불필요한 리소스가 쓰인다. 이를 개선하는 두 가지 방법이 메시지 트리밍과 대화 요약이다.
메시지 트리밍은 처리해야 할 정보량을 줄여 더 빠르고 효율적으로 응답하게 하는 방법으로, 일반적으로 가장 최근 몇 개 메시지만 남기고 오래된 메시지를 삭제한다.
from langchain_core.messages import trim_messages
from langchain_core.runnables import RunnablePassthrough
from operator import itemgetter
# 메시지 트리밍 유틸리티 설정: 최근 메시지 기준으로 최대 2개만 남김
trimmer = trim_messages(strategy="last", max_tokens=2, token_counter=len)
# 트리밍된 대화 이력과 함께 체인 실행
chain_with_trimming = (
RunnablePassthrough.assign(chat_history=itemgetter("chat_history") | trimmer)
| prompt
| chat
)
# 트리밍된 대화 이력을 사용하는 체인 설정
chain_with_trimmed_history = RunnableWithMessageHistory(
chain_with_trimming,
lambda session_id: chat_history,
input_messages_key="input",
history_messages_key="chat_history",
)
strategy="last"는 가장 최근 메시지를 기준으로, max_tokens=2는 메시지를 2개만 남기라는 뜻이다. itemgetter("chat_history") | trimmer로 저장된 이력을 불러와 트리밍한 뒤 프롬프트·모델에 전달하므로, 모델이 처리할 메시지 수를 줄이면서도 최근 맥락은 유지할 수 있다.
대화 요약은 이전 대화를 압축해 중요한 정보만 남기고, 이후 새 질문에 답할 때 요약된 대화만 참조하게 한다.
def summarize_messages(chain_input):
stored_messages = chat_history.messages
if len(stored_messages) == 0:
return False
# 대화를 요약하기 위한 프롬프트 템플릿 설정
summarization_prompt = ChatPromptTemplate.from_messages([
("placeholder", "{chat_history}"), # 이전 대화 이력
("user", "이전 대화를 요약해 주세요. 가능한 한 많은 세부 정보를 포함하십시오."),
])
summarization_chain = summarization_prompt | chat
summary_message = summarization_chain.invoke({"chat_history": stored_messages})
chat_history.clear() # 요약 후 이전 대화 삭제
chat_history.add_message(summary_message) # 요약된 메시지를 대화 이력에 추가
return True
# 대화 요약을 처리하는 체인 설정
chain_with_summarization = (
RunnablePassthrough.assign(messages_summarized=summarize_messages)
| chain_with_message_history
)
# 요약된 대화를 기반으로 새로운 질문에 응답
print(chain_with_summarization.invoke(
{"input": "저에게 어떤 재정적 조언을 해주셨나요?"},
{"configurable": {"session_id": "unused"}},
).content)
summarize_messages()는 저장된 대화가 있으면 요약 프롬프트로 이전 대화를 압축한 뒤, 기존 이력을 지우고 요약된 메시지 하나로 대체한다. 이렇게 하면 AI는 이전의 긴 대화 기록을 모두 기억할 필요 없이 핵심 정보만으로 정확한 응답을 이어갈 수 있다. 다만 요약 과정에서 중요한 정보가 손실되거나 요약의 정확도가 떨어지면 대화 맥락이 왜곡될 수 있으므로 주의해야 한다.
핵심 개념 정리
| 개념 | 한 줄 설명 |
|---|---|
| 랭체인 | LLM 활용 애플리케이션 개발을 위한 오픈소스 프레임워크. 모듈성·통합 용이성·확장된 기능·커뮤니티 지원이 강점 |
| 패키지 구조 | langchain-core(기반) · langchain(체인·에이전트·검색기) · langchain-community(타사 통합) · 파트너 패키지(전용 통합) |
| 생태계 도구 | 랭그래프(그래프 워크플로) · 랭서브(REST 배포) · 랭스미스(모니터링·평가) |
| 오픈AI API vs 랭체인 | 직접 호출은 단순하지만 특정 모델에 종속 / 랭체인은 모듈화되어 모델 전환·확장이 쉬움 |
| 하이퍼파라미터 | temperature(다양성) · max_tokens(길이 제한) · top_p · frequency/presence penalty(반복 억제) · stop sequence |
| 러너블·LCEL | invoke·batch·stream 등 표준 메서드를 갖는 실행 단위(러너블)를 파이프(|)·.pipe()로 선언적으로 연결 |
| 프롬프트 템플릿 유형 | 문자열 / 챗(시스템·사용자·AI 역할) / 메시지 자리 표시자 / 퓨샷(예제 선택기로 유사 예제만 선택) |
| 출력 파서 | 자유 형식 텍스트 → 구조화된 데이터. JSON·Pydantic·CSV 등. 최근엔 함수/도구 호출로 대체되는 추세 |
| 대화 이력 관리 4단계 | 수동 전달 → ChatMessageHistory(저장·재사용) → RunnableWithMessageHistory(자동) → 트리밍/요약(길어진 대화 효율화) |
실무 체크리스트
- [ ] 이 작업이 단순한 단발성 호출인가, 아니면 모델 전환·기능 확장 가능성이 있는가 — 후자라면 오픈AI API 직접 호출 대신 랭체인(LCEL)을 검토했는가?
- [ ] 파트너 통합이 필요할 때
langchain-community가 아니라 전용 파트너 패키지(langchain-openai등)를 쓰고 있는가? - [ ] 온도(temperature)를 일관된 출력이 필요한 곳(분류·요약)과 창의적 출력이 필요한 곳(브레인스토밍)에 서로 다르게 설정했는가?
- [ ] 여러 입력을 처리할 때
invoke()를 반복 호출하는 대신batch()나RunnableParallel을 검토했는가? - [ ] 프롬프트에 예제가 필요한 작업이라면, 예제를 전부 나열하는 대신 예제 선택기로 관련 예제만 골랐는가?
- [ ] 모델 출력을 다음 단계 코드에서 프로그래밍적으로 다뤄야 한다면, 문자열 파싱 대신 출력 파서(또는 도구 호출)로 구조화했는가?
- [ ] JSON 출력을 요구할 때, 모델이 형식을 어길 가능성에 대비한 오류 처리(
RetryWithErrorOutputParser등)를 넣었는가? - [ ] 챗봇이 이전 대화를 기억해야 하는 요구사항이 있다면, 요구 수준에 맞는 메모리 방식(수동/자동/트리밍/요약)을 선택했는가?
- [ ] 대화가 길어질 가능성이 있는 서비스라면 메시지 트리밍이나 요약으로 비용·지연 시간을 관리할 계획이 있는가?
- [ ] 코드에 등장하는 모델 이름(예: 파라미터 예제의 구형 모델)이 현재도 지원되는 모델인지 확인했는가?
연습문제
- 개념. 랭체인의 핵심 패키지
langchain-core·langchain·langchain-community·파트너 패키지가 각각 무엇을 담당하는지 설명하고,langchain을 설치하면 왜langchain-core가 자동으로 함께 설치되는지 근거를 들어 답하라. - 비교. 같은 "주제에 대해 짧은 설명을 요청"하는 기능을 오픈AI API를 직접 호출하는 코드와 랭체인 LCEL로 구현한 코드로 각각 작성했다고 하자. 두 코드에서 모델을 다른 제공사(예: 미스트랄AI)로 바꾸려면 각각 무엇을 수정해야 하는지 비교하라.
- 적용. 사용자가 입력한 여러 주제(예: "인플레이션", "환율", "금리")에 대한 설명을 동시에 받아야 한다고 하자. 러너블 표준 인터페이스 중 어떤 메서드를 쓰는 것이 적절한지 근거와 함께 코드로 제시하라.
- 설계. 금융 상담 챗봇을 만드는데, 사용자가 며칠에 걸쳐 아주 긴 대화를 이어갈 수 있다고 한다.
RunnableWithMessageHistory를 그대로 쓸 때 생기는 문제와, 이를 완화할 수 있는 두 가지 방법(트리밍·요약)의 장단점을 비교해 설계하라. - 판단. 어떤 개발자가 모델의 답변에서 특정 필드(예: 질문·조언)를 프로그램에서 바로 쓰고 싶어 한다. 단순히 응답 문자열을 정규식으로 잘라 쓰는 방법과
PydanticOutputParser를 쓰는 방법을 비교하고, 어느 쪽이 왜 더 안전한지 근거를 들어 판단하라.
최신 동향 (2026-09 기준)
최신 동향 (검증 2026-09-12) — 이 장이 다룬 원리(패키지 역할 분담·LCEL의 러너블 개념·프롬프트/출력 파서/메모리 관리 패턴)는 그대로 유효하다. 다만 이 장이 인용한 구체적 버전·도구·모델 몇 가지는 책 집필 이후 크게 달라졌다.
- 랭체인은 0.3을 지나 1.0대로 넘어갔다. 이 장 §1.2가 다룬 0.3(2024-09, Pydantic 2 전환)은 이후에도 이어져, 현재 랭체인은 정식 1.0 라인을 거쳐 후속 버전을 내고 있고 0.3은 유지 보수(MAINTENANCE) 모드로 전환되어 2026년 12월까지만 보안 패치를 받는다. 또한 초창기 에이전트 실행기였던
AgentExecutor도 유지 보수 모드(EOL 2026-12)로 전환되어, 새 에이전트는 랭그래프 기반 구성 방식이 권장된다. 다행히 이 장의 핵심인 LCEL(파이프 연산자 스타일)은 지금도 폐기되지 않았고 커스텀 체인·RAG 파이프라인의 권장 방식으로 남아 있다. (랭체인 공식 릴리스 정책) - 랭서브(LangServe)는 폐기되었다. §1.1이 소개한 랭서브는 2024년 11월 18일 자로 공식 폐기(deprecated)되었고, 저장소 자체도 이후 보관(archive) 처리되었다. 랭체인 팀은 체인·랭그래프 에이전트를 배포할 때 랭서브 대신 랭그래프 플랫폼(LangGraph Platform)을 쓰도록 권장한다. (langchain-ai/langserve 저장소 공지)
- 프롬프트 허브의 접근 방식이 바뀌었다. §4.5가 다룬 독립
langchainhub패키지와hub.pull()은 더 이상 별도로 관리되지 않고, 그 기능이 랭스미스(LangSmith)의 Prompts 기능으로 흡수되었다 — 프롬프트를 올리고 내려받는 실제 동작은 이제langsmith패키지가 담당한다. 프롬프트를 공유·재사용하는 개념 자체는 이 장의 설명과 같지만, 실제 코드에서 임포트할 패키지가 바뀌었다는 점에 유의해야 한다. (랭체인 공식 문서 — 프롬프트 관리) - §2.3의 표 1-3에 실린 모델 중 세 개는 이제 API에서 쓸 수 없다.
GPT-4.5-preview는 2025년 7월 14일,o1-preview는 2025년 7월 28일(대체:o3),o1-mini는 2025년 10월 27일(대체:o4-mini)에 각각 오픈AI API에서 폐기되었다. 표의 나머지 모델(GPT-4o 계열·Claude 3.x·Gemini·mistral-large·deepseek)도 각 제공사에서 이후 세대가 나왔지만, 그 구체적인 가격·순위 변동까지는 이 검증에서 확인하지 않았다 — 새 모델을 실제로 선택할 때는 각 제공사의 최신 가격표를 다시 확인해야 한다. (OpenAI 공식 Deprecations 문서)
부록 A. 핵심 비교표
오픈AI API 직접 호출 vs 랭체인(LCEL)
| 구분 | 오픈AI API 직접 호출 | 랭체인(LCEL) |
|---|---|---|
| 구조 | 단순한 호출 방식 | 모듈화된 체인 구조 |
| 유연성 | 낮음 — 특정 모델에 종속 | 높음 — 다양한 모델 전환·기능 확장 가능 |
| 코드 복잡도 | 간단함 — 코드 길이 짧음 | 체계적이지만 다소 길어질 수 있음 |
| 재사용성 | 낮음 — 코드 일부를 수정해야 재사용 가능 | 높음 — 모듈화된 구성으로 손쉽게 재사용 |
| 모델 전환 | 제한적 — 코드 수정 필요 | 매우 용이 — 모델 클래스만 교체 |
파이프 연산자(|) vs .pipe() 메서드
| 구분 | 파이프 연산자(|) | .pipe() 메서드 |
|---|---|---|
| 표기 | prompt | model | parser |
prompt.pipe(model, parser) 또는 .pipe() 연쇄 호출 |
| 내부 동작 | __or__ 메서드를 오버로딩해 동작(§3.2 CustomLCEL 참고) |
같은 러너블 인터페이스의 메서드 호출 |
| 여러 단계 한 번에 연결 | 각 단계마다 | 반복 | .pipe(a, b, c)처럼 인자를 나열해 한 번에 연결 가능 |
| 가독성 | 파이프라인 형태를 시각적으로 보여 줌 | 메서드 체이닝에 익숙한 코드 스타일에 자연스러움 |
수동 대화 이력 관리(ChatMessageHistory) vs 자동 대화 이력 관리(RunnableWithMessageHistory)
| 구분 | ChatMessageHistory | RunnableWithMessageHistory |
|---|---|---|
| 이력 추가 | add_user_message()·add_ai_message()를 직접 호출 |
체인을 감싸면 호출마다 자동으로 이력에 추가 |
| 체인 호출 시 전달값 | chat_history.messages를 매번 직접 넘겨야 함 |
session_id만 넘기면 내부에서 이력을 불러와 사용 |
| 세션 구분 | 별도 구현 필요 | session_id로 세션별 이력을 자동 분리 |
| 적합한 상황 | 이력 접근·가공(요약·트리밍 등)을 직접 제어하고 싶을 때 | 세션별 대화를 표준적인 방식으로 간단히 유지하고 싶을 때 |
부록 B. 추천 참고 자료
외부 자료 (Tier 1 공식, 생존·리다이렉트 확인 2026-09-12)
- 랭체인 공식 문서(개요) — §1의 패키지·구성요소 설명의 원출처. 원문이 인용한
python.langchain.com/docs/introduction/주소는 이제docs.langchain.com으로 308 리다이렉트된다. LangChain 공식 문서 - 랭체인 릴리스 정책 — §1.2·최신 동향에서 다룬 버전별 유지 보수 상태의 원출처. Release policy - Docs by LangChain
- 랭체인 표현 언어(러너블) 공식 개념 문서 — §3의 invoke·batch·stream 같은 표준 메서드의 공식 레퍼런스. LangChain runnables 개념 문서
- 프롬프트 관리(허브 후속) 공식 문서 — §4.5·최신 동향에서 다룬,
langchainhub폐기 이후의 공식 안내. Manage prompts programmatically - 랭서브 저장소 폐기 공지 — §1.1·최신 동향에서 다룬 랭서브 폐기 사실의 1차 출처. langchain-ai/langserve
- OpenAI 모델 Deprecations 공식 문서 — §2.3 표의 모델 중 폐기된 세 모델의 폐기 일자·대체 모델 원출처. OpenAI API Deprecations
더 해보기 — 읽고 끝내지 않으려면
- §3.2의
CustomLCEL예제를 그대로 실행해 보고,__or__대신__add__(+연산자)로 바꿔 똑같은 파이프라인을 만들어 본다. 랭체인의|가 왜 특별한 연산자가 아니라 파이썬의 일반적인 오버로딩 기능이었는지 체감할 수 있다. - §4.4의 예제 선택기
k값을 1에서 2로 늘려 실행해 보고, 선택되는 예제와 최종 프롬프트가 어떻게 달라지는지 비교해 본다. - §6.4의
trim_messages(max_tokens=2, ...)값을 4·6으로 늘려 가며 실행해, 트리밍 강도에 따라 챗봇이 몇 턴 전 대화까지 기억하는지 직접 확인해 본다.
본 책 연계 챕터
| 챕터 | 이 장이 다루지 않은 것 |
|---|---|
| 2장 §5 RAG 챗봇 구현 | 이 장의 LCEL·프롬프트·출력 파서·메모리를 실제 RAG 챗봇(검색+생성)으로 조합하는 방법. 이 장은 검색·임베딩·벡터 저장소를 다루지 않는다 |
| 3장 §2 멀티모달 RAG 구현 방법 | 텍스트가 아닌 이미지·표 등 복합 데이터를 다루는 방법. 이 장의 프롬프트·체인은 텍스트 입출력만 다룬다 |
| 4장 §2 질의 변형 | 사용자의 원 질문을 다중 질의·가상 문서 임베딩으로 변형해 검색 품질을 높이는 기법. 이 장은 프롬프트 작성만 다루고 검색 자체를 다루지 않는다 |
| 6장 §1 랭그래프의 구성요소 | 이 장의 러너블·체인보다 더 복잡한 분기·루프·상태 저장이 필요한 워크플로를 그래프로 설계하는 방법(§1.1에서 이름만 소개했다) |
| 7장 §2 에이전트 RAG | 모델이 스스로 도구 호출 여부를 판단하는 에이전트 패턴. 이 장의 체인은 미리 정해진 순서대로만 실행된다 |
| 8장 §4 로컬 LLM Qwen 파인튜닝하기 | 모델 자체의 가중치를 바꾸는 방법. 이 장은 프롬프트·파라미터로 기존 모델을 "쓰는" 법만 다룬다 |
부록 C. 연습문제 풀이
-
(패키지 역할 분담)
langchain-core는 대규모 언어 모델·벡터 저장소·검색기의 기본 구조와 LCEL을 담는 기반 패키지이고,langchain은 그 위에서 체인·에이전트·검색기 전략을 구현한다.langchain-community는 커뮤니티가 유지 관리하는 다양한 타사 통합을 모아 두며, 파트너 패키지는 오픈AI·앤트로픽처럼 자주 쓰는 서비스별로 분리된 전용 통합이다.langchain을 설치하면langchain-core가 자동으로 함께 설치되는 이유는langchain이 체인·에이전트를 구현하는 데langchain-core가 정의한 기본 구조(러너블·LCEL 등)에 직접 의존하기 때문이다 — 그림 1-2에서langchain이langchain-core를 가리키는 화살표가 이 의존 관계를 나타낸다. -
(모델 전환 비교) 오픈AI API를 직접 호출한 코드는
client = openai.OpenAI()와client.chat.completions.create(model="gpt-4o-mini", ...)처럼 오픈AI 전용 클라이언트와 메서드에 종속되어 있어, 미스트랄AI로 바꾸려면openai라이브러리 호출부 전체를 미스트랄AI 클라이언트·API 형식으로 다시 작성해야 한다. 반면 랭체인 LCEL 코드는model = ChatOpenAI(model="gpt-4o")를model = ChatMistralAI(api_key=MISTRAL_API_KEY)로 한 줄만 바꾸면 되고, 체인 구성(prompt | model | output_parser)과 나머지 코드는 그대로 유지된다. 표준화된 러너블 인터페이스 덕분에 모델 클래스만 교체하면 되는 것이 핵심 차이다. -
(배치 처리) 여러 주제를 동시에 처리해야 하므로
invoke()를 세 번 반복 호출하는 대신batch()메서드를 쓰는 것이 적절하다.invoke()를 반복하면 각 호출이 순차적으로 완료를 기다려야 하지만,batch()는 여러 입력을 한 번에 전달해 동시에 처리하도록 최적화되어 있기 때문이다.python chain.batch([ {"topic": "인플레이션"}, {"topic": "환율"}, {"topic": "금리"}, ]) -
(긴 대화 설계)
RunnableWithMessageHistory만 쓰면 대화가 길어질수록 매 호출마다 전체 이력을 모델에 그대로 전달하게 되어, 처리해야 할 토큰 수가 계속 늘어나 응답이 느려지고 비용이 커지며 결국 모델의 문맥 길이 한계에 부딪힐 수 있다. 트리밍은 최근 몇 개 메시지만 남기므로 구현이 간단하고 비용이 예측 가능하지만, 오래된 맥락(예: 초반에 설정한 재무 목표)을 완전히 잃어버릴 수 있다. 요약은 오래된 대화를 압축해 핵심 정보를 남기므로 긴 맥락을 어느 정도 유지할 수 있지만, 요약 과정에서 세부 정보가 누락되거나 왜곡될 위험이 있고 요약 자체에 추가 모델 호출 비용이 든다. 초반 목표처럼 오래 유지해야 할 정보가 중요한 상담 챗봇이라면 트리밍보다 요약이, 혹은 둘을 함께 쓰는 방식이 더 적합하다. -
(구조화 방식 비교) 응답 문자열을 정규식으로 잘라 쓰는 방법은 모델이 항상 예상한 형식(예: "질문: ~ 답변: ~")으로 답한다는 보장이 없어, 형식이 조금만 달라져도 정규식이 깨지고 오류를 조용히 지나칠 위험이 있다.
PydanticOutputParser는get_format_instructions()로 모델에게 명확한 출력 형식을 지시하고, 응답을FinancialAdvice같은 Pydantic 모델에 맞춰 파싱하면서model_validator로 필드 값의 유효성(예: 질문이 물음표로 끝나는지)까지 검증한다. 형식을 어기면 명시적으로 예외가 발생하므로 오류를 바로 감지할 수 있어, 정규식으로 직접 자르는 방법보다 더 안전하다.
클릭하거나 Space를 눌러 뒤집기